package com.xiyuan.smartutils.logging.appender;

import com.xiyuan.smartutils.DateTimes;
import com.xiyuan.smartutils.Files;
import com.xiyuan.smartutils.StrUtils;
import com.xiyuan.smartutils.Validates;
import com.xiyuan.smartutils.logging.LogAppender;
import com.xiyuan.smartutils.logging.LogRecord;
import com.xiyuan.smartutils.logging.LoggerManager;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/**
 * the description for class FileHandler
 *
 * @author xiyuan-lgz 2022-10-19 @version v1.0.0 新建与整理
 */
public class FileAppender extends LogAppender {
    
    private PrintStream output;
    
    private String dirPath;
    private String filePath;
    private File dir;
    private File file;// 文件名
    
    private int seq;
    private long cleanTime;
    private volatile boolean cleaning;
    
    private static final int CLEAR_DURATION = 6 * 3600;// 文件清理间隔 6 个小时清理一次
    
    private void clearHistory(int day, String fileName, long time) {
        
        if (Math.abs(time - cleanTime) < CLEAR_DURATION || day <= 0) {
            return;
        }
        
        if (cleaning) {
            return;
        }
        
        
        try {
            
            // 关闭当前文件流
            cleaning = true;
            closeFileOut();
            cleanTime = time;
            seq = 0;
            
            // 删除历史文件
            String limit = DateTimes.getPreviousDateString(day);
            File dir = new File(config.getDir());
            boolean hasFail = false;
            for (File file : dir.listFiles()) {
                if (!file.isFile() || file.getName().length() < 10 || file.getName().indexOf(fileName) != 11) {
                    continue;
                }
                
                try {
                    if (limit.compareTo(file.getName().substring(0, 10)) >= 0) {
                        if (!Files.deleteFile(file)) {
                            hasFail = true;
                        }
                    }
                }
                catch (Throwable e) {
                    LoggerManager.reportError(e);
                }
            }
            
            if (hasFail) { // 如果 有文件删除失败，则删除频率 加快1倍
                cleanTime -= CLEAR_DURATION / 2;
            }
        }
        finally {
            cleaning = false;
        }
    }
    
    private void closeOut() {
        try {
            // 关闭上一个文件
            if (output != null) {
                output.flush();
                output.close();
            }
        }
        catch (Throwable e) {
        }
        finally {
            output = null;
        }
    }
    
    private void closeFileOut() {
        closeOut();
        file = null;
    }
    
    /**
     * 文件滚动
     *
     * @param path    当前文件路径
     * @param current 当前文件名称
     * @return
     */
    private String scrollFile(String path, String current) {
        // 检查文件滚动
        
        if (config.getMaxFileSize() > 0) {
            
            long maxLength = config.getMaxFileSize();
            int fileIndex = seq;
            
            if (fileIndex == 0) {
                // 启动时 往文件夹里查询查找最后一个文件进行追加
                
                fileIndex = Arrays.stream(dir.listFiles((dir, name) -> name.indexOf(current) == 0))
                        .map(f -> {
                            String name = f.getName();
                            int idx = name.lastIndexOf(".");
                            if (idx == -1) {
                                return -1;
                            }
                            
                            name = name.substring(idx + 1);
                            
                            try {
                                return Integer.parseInt(name);
                            }
                            catch (NumberFormatException e) {
                                return -1;
                            }
                        }).max(Integer::compareTo).orElse(0);
            }
            
            if (fileIndex < 0) {
                File file = new File(path);
                if (file.length() >= maxLength) {
                    fileIndex = 1;
                }
            }
            
            if (fileIndex > 0) {
                File file = new File(path + "." + fileIndex);
                if (file.length() >= maxLength) {
                    fileIndex++;// 文件记录序号+1
                }
            }
            
            if (fileIndex > 0 && fileIndex != seq) {
                this.closeFileOut();
            }
            
            if (fileIndex > -1) {
                this.seq = fileIndex;
            }
            
            if (seq > 0) {
                path = path + "." + seq;
            }
        }
        
        return path;
    }
    
    private void openFile() throws Exception {
        if (!this.file.exists()) {// 不存在就创建一个
            closeOut();
        }
        
        if (this.output == null) {
            String charset = config.getEncoding().name();
            this.output = new PrintStream(new FileOutputStream(file, true), true, charset);
            if (StandardCharsets.UTF_8.name().equals(charset)) {// 加上UTF-8的BOM，保证Windows和Linux都能识别
                this.output.write(0xEF);
                this.output.write(0xBB);
                this.output.write(0xBF);
                this.output.flush();
            }
        }
    }
    
    private synchronized boolean checkFile() throws Exception {
        String dirPath = config.getDir();
        if (this.dir == null || !dirPath.equals(this.dirPath)) {
            this.dirPath = dirPath;
            this.dir = new File(dirPath);
        }
        
        Files.mkdir(dir);
        
        String fileName = this.config.getFileName();
        if (Validates.isBlank(fileName)) return false;
        
        // 整理文件路径
        String date = DateTimes.getDateString();
        int day = this.config.getKeepDay();
        if (day > 0) {
            fileName = date + "-" + fileName;
        }
        String path = StrUtils.addEndsWith(dir.getCanonicalPath(), "/") + fileName;
        
        
        // 清理历史文件
        this.clearHistory(day, this.config.getFileName(), System.currentTimeMillis() / 1000);
        
        
        // 文件滚动
        path = this.scrollFile(path, fileName);
        
        
        // 检查文件路径是否被改动
        if (!path.equals(filePath)) {
            this.closeFileOut();// 文件路径被改动
            this.filePath = path;
        }
        
        if (this.file == null) {
            this.file = new File(path);
        }
        
        // 创建文件
        this.openFile();
        
        return true;
    }
    
    public void append(LogRecord record) {
        if (!config.isOutFile()) {
            return;// 没有设置文件输出
        }
        
        if (!config.isEnableLevel(record.getLevel())) {
            return;// 输出级别不支持
        }
        
        try {
            if (!checkFile()) return;
            
            print(record, this.output);
            
        }
        catch (Throwable e) {
            LoggerManager.reportError(e);
        }
    }
}
